
#include "../include/coroutine.h"
#include "coroutine_imp.h"

inline coroutine* malloc_coroutine(void)
{
	void* p =malloc(sizeof(coroutine));

	if(p == NULL)
		return 0;

	memset(p, 0, sizeof(coroutine));

	return (coroutine*)p;
}

inline void free_coroutine(coroutine* c)
{
	free(c);
}

#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)

VOID __stdcall FiberFunc(LPVOID lpParameter);


inline int init_main_coroutine(coroutine* c)
{
	c->ctx = ConvertThreadToFiber(NULL);
	return (c->ctx==NULL) ? -1 : 0;
}

inline int setup_coroutine(coro_schedule* tctx, coroutine* c, size_t ssize)
{
	c->ctx = CreateFiber(ssize, &FiberFunc, tctx);
	c->stk_size = ssize;
	return (c->ctx==NULL) ? -1 : 0;
}

inline int cleanup_coroutine(coroutine* c)
{
	DeleteFiber(c->ctx);
	c->ctx = 0;
	c->stk_size = 0;
	return 0;
}

inline int swap_context(coro_schedule* tctx, coroutine* from, coroutine* to)
{
	tctx->coro_curr = to;
	SwitchToFiber(to->ctx);
	return 0;
}


void __stdcall FiberFunc(LPVOID lpParameter)
{	
	coro_schedule* tctx = (coro_schedule*)lpParameter;
	coroutine* c = tctx->coro_curr;

	c->func(c->data);
	c->status = coro_stat_dead;
	bd_push_front(&tctx->coro_dead_list, &c->node);
	swap_context(tctx, c, tctx->coro_main);
}

#else

#include <sys/mman.h>
#include <unistd.h>

void FiberFunc(coro_schedule* tctx);

inline int init_main_coroutine(coroutine* c)
{
	return getcontext(&c->ctx);
}

inline int setup_coroutine(coro_schedule* tctx, coroutine* c, size_t ssize)
{
	if (getcontext(&c->ctx) == -1)
		return -1;

	void* sp = malloc(ssize);
	if ( !sp )
	{
		return -2;
	}

	c->stk_size = ssize;
	c->ctx.uc_link = NULL;
	c->ctx.uc_stack.ss_sp = sp;
	c->ctx.uc_stack.ss_size = ssize;
	c->ctx.uc_stack.ss_flags = 0;

	makecontext(&c->ctx, (void (*)(void))&FiberFunc, 1, tctx);

	return 0;
}

inline int cleanup_coroutine(coroutine* c)
{
	if(c->ctx.uc_stack.ss_sp)
	{
		c->stk_size = 0;
		
	
		void* sp = c->ctx.uc_stack.ss_sp;
		free( sp );
		
		c->ctx.uc_stack.ss_sp = 0;
		c->ctx.uc_stack.ss_size = 0;
	}
	return 0;
}

inline int swap_context(coro_schedule* tctx, coroutine* from, coroutine* to)
{
	tctx->coro_curr = to;
	return swapcontext(&from->ctx, &to->ctx);
}

void FiberFunc(coro_schedule* tctx)
{
	coroutine* c = tctx->coro_curr;
	c->func(c->data);
	c->status = coro_stat_dead;
	bd_push_front(&tctx->coro_dead_list, &c->node);
	swap_context(tctx, c, tctx->coro_main);
}

#endif
/////////////////////////////////

coroutine* create_coroutine(coro_schedule* tctx, size_t ssize)
{
	coroutine* c = malloc_coroutine();
	if(c == NULL)
	{
		return NULL;
	}

	if(setup_coroutine(tctx, c, ssize) == -1)
	{
		free_coroutine(c);
		return NULL;
	}

	return c;
}

coro_schedule_t coro_schedule_init(size_t stack_size, size_t max_coro_size)
{
	coro_schedule* tctx = (coro_schedule*)malloc(sizeof(coro_schedule));
	if (tctx == NULL)
		return 0;

	memset(tctx, 0, sizeof(coro_schedule));

	tctx->coro_stack_size = stack_size;
	tctx->max_coro_size = max_coro_size;

	coroutine* c = malloc_coroutine();
	tctx->coro_main = c;
	tctx->coro_curr = c;

	if (init_main_coroutine(c) == 0)
	{
		for (size_t i = 0; i < max_coro_size; ++i)
		{
			c = create_coroutine(tctx, tctx->coro_stack_size);
			if (c == NULL)
				break;
			c->status = coro_stat_null;
			bd_push_front(&(tctx->coro_cache_list), &c->node);
		}

		return tctx;
	}

	coro_schedule_cleanup(tctx);
	return 0;
}

void coro_schedule_cleanup(coro_schedule_t t)
{
	if (t == NULL)
		return;

	coro_schedule* tctx = (coro_schedule*)t;
	free_coroutine(tctx->coro_main);
	tctx->coro_main = NULL;
	tctx->coro_curr = NULL;

	while (coroutine* c = (coroutine*)bd_pop_back(&tctx->coro_cache_list))
	{
		cleanup_coroutine(c);
		free_coroutine(c);
	}

	while (coroutine* c = (coroutine*)bd_pop_back(&tctx->coro_dead_list))
	{
		cleanup_coroutine(c);
		free_coroutine(c);
	}

	while (coroutine* c = (coroutine*)bd_pop_back(&tctx->coro_ready_list))
	{
		cleanup_coroutine(c);
		free_coroutine(c);
	}

	free(tctx);
}



coroutine_t coro_create(coro_schedule_t t, void(*func)(void*), void* data)
{
	coro_schedule* tctx = (coro_schedule*)t;

	coroutine* c = NULL;
	if (tctx->coro_cache_list.count > 0)
	{
		c = (coroutine*)bd_pop_back(&tctx->coro_cache_list);
		assert(c);
	}
	else
	{
		return NULL;
	}

	if (c != NULL)
	{
		c->func = func;
		c->data = data;
		c->status = coro_stat_ready;
		bd_push_front(&tctx->coro_ready_list, &c->node);
	}
	return c;
}

int coro_yield(coro_schedule_t t)
{
	coro_schedule* tctx = (coro_schedule*)t;
	coroutine* c = tctx->coro_curr;
	if(c == NULL)
	{
	return 0;
	}
	if (tctx->coro_curr->status != coro_stat_run)
	{
		return 0;
	}
	c->status = coro_stat_wait;
	return swap_context((coro_schedule*)t, c, tctx->coro_main);
}

int coro_resume(coro_schedule_t t, coroutine_t co)
{
	coroutine* c = (coroutine*)co;
	if(c == NULL)
		return -1;

	coro_schedule* tctx = (coro_schedule*)t;
	if(c == tctx->coro_curr)
		return 0;

	if (c->status != coro_stat_wait)
	{
		return 0;
	}
	c->status = coro_stat_ready;
	bd_push_front(&tctx->coro_ready_list, &c->node);

	return 0;
}

coroutine_t coro_current(coro_schedule_t t)
{
	coro_schedule* tctx = (coro_schedule*)t;
	return tctx->coro_curr;
}

coroutine_t coro_main(coro_schedule_t t)
{
	coro_schedule* tctx = (coro_schedule*)t;
	return tctx->coro_main;
}


void coro_update(coro_schedule_t t)
{
	coro_schedule* tctx = (coro_schedule*)t;

	while(coroutine* c=(coroutine*)bd_pop_back(&tctx->coro_ready_list))
	{
		c->status = coro_stat_run;
		swap_context(tctx, tctx->coro_main, c);
	}

	while(coroutine* c=(coroutine*)bd_pop_back(&tctx->coro_dead_list))
	{
		c->status = coro_stat_null;
		bd_push_front(&tctx->coro_cache_list, &c->node);
	}
}

void coro_set_data(coroutine_t co, void* d)
{
	coroutine* c = (coroutine*)co;
	c->data = d;
}

void* coro_get_data(coroutine_t co)
{
	coroutine* c = (coroutine*)co;
	return c->data;
}

int coro_ready_count(coro_schedule_t t)
{
	coro_schedule* tctx = (coro_schedule*)t;
	return (int)tctx->coro_ready_list.count;
}
